iT邦幫忙

2026 iThome 鐵人賽

DAY 27
0
自我挑戰組

一鍵完成六套開源防禦系統整合系列 第 27 篇

Vector:三種告警變成一種事件

  • 分享至 

  • xImage
  •  

防禦節點(主機 B)上除了 WAF,還裝了幾個別人寫的系統。「搭配元件篇」這一系列每篇只談一個,不深入它的設定細節,只回答三件事:它是什麼、它在主機 A 與主機 B 之間佔哪一段、少了它整體會缺什麼。本篇是 Vector。它和前七篇不同的地方是:程式是別人寫的,但它做的每一件事都寫在安裝包自己的一份 461 行設定檔裡,所以這一篇會多講一點設定檔。 主機與位址沿用防禦節點那一套:主機 A(192.168.0.111)是 BeakPlatform,主機 B(192.168.0.112)是防禦節點。

先講結論。 Vector 是一個讀紀錄、改格式、往外送的管線程式。在主機 B 上,它是唯一把 WAF、Suricata、CrowdSec 三種告警讀進來的東西,把它們改寫成同一種格式,然後分兩路送出:一路全量進 ClickHouse,一路經過過濾與封頂才交給 od-bridge 送往主機 A。它自己不偵測任何東西、不擋任何東西,但「平台上會出現幾張案件」這個數字,絕大部分是它決定的。少了它,WAF 與 Suricata 的告警只會留在主機 B 的檔案裡,主機 A 一筆都收不到。

另外有一件事值得先知道:它送往主機 A 的那一路,在主機 A 連不上的時候會把事件留在記憶體裡一直重試,重試的事件排在隊伍前面,後面的全部等著。這段期間 ClickHouse 那一路照常;等到隊伍長到一定程度,整條管線會停下來,連 ClickHouse 那一路也停。主機 A 恢復之後,排隊的事件會一口氣送出。重啟 Vector 容器則會把排隊中的事件丟掉。這是本系列第一次在實測裡看到「一個元件的下游壞了,會拖住它的另一條輸出」,第四節講。

一、它是什麼

Vector 是 Datadog 維護的開放原始碼資料管線(授權 MPL-2.0),用 Rust 寫成,一個執行檔,設定檔決定它讀什麼、怎麼改、送到哪。它和 Fluent Bit、Logstash 是同一類東西。本安裝包用的是官方容器映像 timberio/vector:0.41.1-alpine(2024 年 9 月發布),官方未修改的版本;所有行為都來自安裝包自己寫的 vector/vector.yaml,沒有任何外掛。

它在本安裝包裡的樣子:

項目 內容
是否安裝 一定會裝,沒有關閉選項。它是事件路徑上的必經元件,--no-ui 不影響它
設定檔 /opt/integrated-waf/vector/vector.yaml,461 行,唯讀掛進容器。分 sources、transforms、sinks 三段,第三節逐段講。改了之後 docker compose restart vector 才生效,install.sh --reconfigure 看到檔案變了會自己重啟
它讀什麼 Suricata 的 eve.json(資料卷)、WAF 主站與歡迎頁的 audit.log(主機目錄 waf/log/),兩者都唯讀掛進容器;CrowdSec 的 http 通知外掛送到它的 8689 埠;測試用的 8688 埠
它送去哪 ClickHouse(容器網路內 clickhouse:8123)與 od-bridge(主機網路 host.docker.internal:8500)
誰能連它 8686 是它的查詢 API,由主機 B 的防火牆限制在 --admin-ips 與主機 A;8688 只綁本機 127.0.0.1;8689 沒有發布到主機,只有同一個容器網路內的 CrowdSec 連得到
可調的值 封頂的門檻與時窗、可信代理名單、ClickHouse 帳密、od-bridge 的 token,都在 .env,設定檔用 ${變數名:-預設值} 引用
它自己的資料 只有「每個檔案讀到哪裡」的紀錄(checkpoint),放在資料卷 vector-data,每個來源一個幾百 bytes 的 JSON。事件本身不落地
資源 實測一台節點閒置時約佔 80 MB 記憶體;映像檔 188 MB

「事件本身不落地」這一條要記住。它的輸入是檔案,輸出是兩個網路服務,中間只有記憶體;它不是資料庫,也不是佇列。這決定了第四節會看到的行為。

二、它接在哪裡

https://ithelp.ithome.com.tw/upload/images/20261011/20184261dWTa7rSdGh.png
(圖01:Vector 的位置。黑色實線是事件的路徑,灰色是被丟掉的事件與測試注入口,藍色虛線是 od-bridge 簽章後送往主機 A 的那一段,灰色細線是管理者查它狀態的方向。)

圖上有三件事要看。

  • 三個來源進來的方式不一樣。 Suricata 與 WAF 是「寫檔,Vector 跟著檔尾讀」,所以 EveBox 那一篇說的「和 Vector 讀同一個檔案」就是這裡;CrowdSec 是「主動 POST 給 Vector」,走容器網路內的 8689 埠,主機上看不到這個埠。三種告警的原始格式完全不同,進了 Vector 之後只剩一種。
  • 兩個出口拿到的不是同一份東西。 ClickHouse 拿正規化之後的全部;od-bridge 拿的是過濾與封頂之後剩下的。圖右下角那個灰色的框是兩者的差:被丟掉的事件還在 ClickHouse,只是不會變成主機 A 的案件。第 2 篇說「平台上有幾張案件永遠不等於發生了幾次攻擊」,差額就在這裡。
  • 8686 埠是它的查詢介面,沒有登入。 能連到它的人可以看拓樸、看每個元件處理了幾筆、還可以即時看流過的每一筆事件,裡面有攻擊者 IP、網址、User-Agent。它由主機 B 的防火牆限制來源,和 Grafana、EveBox 同一組名單。第六節會用它。

三、設定檔的三段

Vector 的設定檔分 sources(讀)、transforms(改)、sinks(送)三段,每一段裡是一個個有名字的元件,用 inputs 指明接在誰後面。vector.yaml 一共 4 個來源、17 個轉換、2 個出口。下面不逐個講,只講讀者會想知道「為什麼這樣」的地方。

來源:四個,三種讀法

來源 型別 怎麼讀
suricata_eve file /var/log/suricata/eve.json。從檔尾開始讀,超過 600 秒沒更新的檔案不理。讀到哪裡記在 checkpoint,重啟後接著讀
modsec_audit file /var/log/modsec/audit*.log,一次吃進主站與歡迎頁兩個 WAF 的紀錄,排除 .gz 與輪替後的舊檔。靠「第一行的檢查碼」辨認檔案,所以每小時的輪替(主機上 cron 複製後清空)不會讓它重讀或漏讀整個檔;但清空的那一瞬間還沒讀走的幾行會不見,安裝包的輪替腳本自己註明了這一點
crowdsec_alerts http_server 監聽 8689,CrowdSec 的 http 通知外掛把命中的 alert 以 JSON 陣列 POST 過來,一筆 alert 拆成一筆事件。這條線在 2026 年 10 月補上,之前 CrowdSec 的偵測結果到不了主機 A(第 2 篇第四節)
http_test http_server 監聽 8688,只綁本機。install.sh --test-event 就是往這裡丟一筆假事件,用來驗整條鏈

正規化:三段 remap,各自對付一種格式

三種告警各有一段轉換程式(Vector 的語言叫 VRL),把原始欄位搬進同一套結構。輸出長什麼樣第 3 篇第一節印過;這裡列三段各自的對映規則,因為它們不一樣,而且每一條都有理由:

項目 Suricata WAF(ModSecurity) CrowdSec
先過哪道篩 只留 event_type=alert 且有來源與目的 IP。解碼類告警(EveBox 篇講的 truncated)沒有 IP,主機 A 一律拒收,所以留在 ClickHouse 就好 只留「有命中規則」或「回應碼 ≥ 400」的交易 只留 CrowdSec 自己偵測出來的(決策的 origin 是 crowdsec),且不是模擬模式、來源是單一 IP。手動用 cscli 下的決策、od-bridge 寫進去的平台決策都不算
source_system suricata coraza。這是早期設計時留下的名字,WAF 引擎後來換成 ModSecurity,字串沒有改,因為主機 A 的金鑰範圍與事件路由認的是它 crowdsec
event_class network_activity web_activity detection_finding
嚴重度 Suricata 的 1 高、2 中、3 低、4 資訊,對映成 OCSF 的 4、3、2、1 整筆交易裡最嚴重的那一條規則,syslog 等級 0~2 對映成 4,3 → 3,4 → 2,其餘 1。沒有命中任何規則、只是回應碼 ≥ 400 的交易,等級是 1 CrowdSec 的 alert 沒有嚴重度欄位。能到這裡的都是累積到門檻、它自己判定該封鎖的行為,一律算 3;情境名含 cve 的算 4
rule_id 規則的 signature_id 第一條命中規則的 id。沒有命中規則時是空字串;這類事件只留在 ClickHouse,不送主機 A(下一段) 情境名,例如 crowdsecurity/ssh-bf
攻擊者 IP 直接取 src_ip。Suricata 自己的 xff 模組已經在它內部把 src_ip 換成 CF-Connecting-IP(第 1 篇) 先看這筆交易的 TCP 來源是不是可信代理(TRUSTED_INGRESS_LIST,預設只有 cloudflared 容器的 172.18.0.250):是,才採信 CF-Connecting-IP 或 XFF 第一段;不是,記 TCP 來源。國別欄位同理,只有可信來源帶來的才留 alert 裡的來源 IP
發生時間 告警自己的時間戳 交易自己的 time_stamp。它是 Fri Oct 9 14:57:15 2026 這種不帶時區的格式,設定檔頂層的 timezone: UTC 就是為它而設;解析不到才退回 Vector 讀到這一行的時間 alert 的 start_at
correlation_id 每筆新產生一個 UUID 每筆新產生一個 UUID 用 alert 自己的 uuid。同一筆 alert 被重送時,主機 A 認得是重複
target.service 空 看紀錄來自哪個檔案:waf-nginx(主站)或 waf-welcome(歡迎頁) 原始記錄裡的 service 欄位

WAF 那一欄的攻擊者 IP 歸因為什麼要在 Vector 做、不在 nginx 做,設定檔裡有一段註解:ModSecurity 讀到的是原始的請求標頭,nginx 的 set_real_ip_from 改的是 nginx 自己看到的客戶端位址,改不到 ModSecurity 寫進紀錄的那一份。所以「誰說的話可以信」這個判斷,只能在讀紀錄的這一端做。總覽那一篇第三節第 3 條講的就是這張名單。

還有一件要對照 WAF 的設定才看得懂的事:WAF 這一路放行「回應碼 ≥ 400 但沒有命中規則」的交易,不是 Vector 自己的主意,而是照抄 ModSecurity 的稽核門檻 SecAuditLogRelevantStatus "^(?:5|4(?!04))",也就是 4xx(404 除外)與 5xx 的交易即使沒有命中規則也會寫進 audit log。這類交易是後端自己回的,例如登入失敗的 401、後端不通的 502,WAF 什麼都沒做。它們會以嚴重度 1、rule_id 空白的樣子進 ClickHouse,查 finding_rule_id = '' 的 coraza 事件就能看到。intake_filter 把它們擋在主機 B,理由有三:正規化後的事件不帶 HTTP 狀態碼,主機 A 分不出 502 和 401;rule_id 空白不能併案,送過去就是每筆一張單;它只在有外部流量撞上來時才出現,當可用性偵測靠不住。登入失敗交給 CrowdSec 的暴力情境累積到門檻再報一筆,後端不通交給健康檢查,兩者都比這條路徑合適。

過濾與封頂:只在送往主機 A 的那一路

正規化之後,事件分成兩路。往 ClickHouse 的一路只做一件事,把常查的欄位抽成寬表的欄、整筆 JSON 塞進 raw 欄,然後送。往 od-bridge 的一路要過四道:

元件 規則 為什麼
intake_filter 攻擊者是內網位址(RFC 1918、本機)且 目標也是內網的,丟。suricata 嚴重度 1 的,丟。WAF 事件不看嚴重度 內網對內網是自家服務互連,不是攻擊。WAF 是在線上實際擋下的,一筆都是證據;Suricata 是旁路,資訊級的太多
intake_throttle_*(分來源) 桶=來源|規則|攻擊者 IP。suricata 每桶 1 筆/3600 秒;coraza 每桶 5 筆/300 秒;其他(CrowdSec、測試口)每桶 1 筆/300 秒 Suricata 同一條規則對同一個 IP 重複性極高,收最緊;WAF 放寬一點讓 SOC 看得到攻擊的強度
intake_global_throttle 不分桶,全部加起來 8 筆/60 秒 分來源封頂是各 IP 各有配額,擋不住分散式掃描的總量。每張案件在主機 A 都會啟動一個流程,這一道保護的是主機 A
intake_throttled_clean 刪掉分桶用的內部欄位 不讓它污染送出去的 payload

封頂的四個數字(兩個門檻、兩個時窗)加上全域那組,都是 .env 裡的 THROTTLE_*,改了重啟 Vector 就生效。有兩件事是 Vector 的 throttle 元件本身的性質,設定檔改不掉:

  • 超額的事件是被丟掉,不是被合併。 Vector 的文件寫得很直接:「rate limiter 滿了之後進來的事件一律丟棄」。所以主機 A 看到的「同一個 IP 五分鐘內五筆」不是「這五分鐘共五筆」,是「至少五筆」。真實的次數在 ClickHouse。
  • 它用的是連續補充的演算法,不是整分鐘歸零的計數器。 8 筆/60 秒的意思是每 7.5 秒補回一格、最多累積 8 格,所以可以一次爆 8 筆,之後每 7.5 秒只能再過一筆。這個狀態只在記憶體,重啟 Vector 就歸零。

出口:兩個,性格相反

出口 ClickHouse ch_events od-bridge bridge_intake
送到哪 容器網路內 http://clickhouse:8123,寫 secstack.events 表 主機網路 http://host.docker.internal:8500/events,od-bridge 收到後簽章轉送主機 A
批次 湊 500 筆或等 5 秒,一次送 一筆一送,最多等 1 秒
認證 ClickHouse 帳密(.env) Authorization: Bearer,token 是安裝時產生的 BRIDGE_INGEST_TOKEN,od-bridge 用同一個值比對
送不出去 兩個出口都是 Vector 的預設重試策略:回應 408、429、5xx(501 除外)或連不上就重試,從 1 秒開始退避、最長 30 秒一次,次數沒有上限。每個出口前面有一個 500 筆的記憶體緩衝,滿了就往上游施壓,不丟 (同左)
啟動時檢查對方活著沒 都關掉(healthcheck.enabled: false),所以主機 A 不在、ClickHouse 還沒起來,Vector 都照常啟動,之後再慢慢重試 (同左)

od-bridge 那一路的回應碼是主機 A 的回應碼原樣轉回來(5xx 一律轉成 502),所以主機 A 的每一種答案都會變成 Vector 的行為:201 建案、200 重複,Vector 當成功;400 格式不對、403 金鑰範圍不對、422 沒有路由規則,Vector 當失敗但不重試,這筆就沒了,但 od-bridge 的 /forwards 頁會留一行;429 主機 A 限流(一把金鑰每分鐘 100 筆),Vector 等一下再送;連不上主機 A,od-bridge 回 502,Vector 一直重試。最後這種就是第四節。

四、主機 A 不通的時候

主機 A 關機、重啟、或主機 B 到主機 A 的網路斷了,Vector 這一邊會發生什麼,從文件只能讀到原則,所以實測了一次。用 Vector 同一個版本的執行檔,在另一台機器上跑一個最小的拓樸:一個注入口、一個轉換、兩個出口,一個出口寫檔(代替 ClickHouse),另一個送到一個沒有人聽的埠(代替連不上的主機 A)。設定全部用預設值,和安裝包一樣。

做的事 看到的
下游不通,一次灌 500 筆進來,連灌 21 次(10,500 筆) 前 21 次注入口都立刻回 200。寫檔那一路寫到 1,500 筆就停住,之後再灌也不再增加。第 22 次注入口 10 秒內沒有回應
停住之後等 20 秒 寫檔那一路還是 1,500 筆。不是慢,是停了
把拓樸換成和安裝包一樣的形狀(出口前面多了過濾、分桶、路由、兩道封頂、清理五個元件,封頂門檻調到不會丟),同樣灌 寫檔那一路寫到 5,000 筆停住。中間每多一個元件,元件之間的通道就多容納一些
把下游打開 25 秒內 10,500 筆全部送達下游;寫檔那一路也補到 10,500 筆
下游不通,灌 50 筆,kill -9 Vector,打開下游,重啟 Vector 下游收到 0 筆。排隊中的 50 筆隨著程序一起消失
同樣 50 筆,改送 SIGTERM(docker compose stop 送的就是這個) Vector 又活了 60 秒才退出,期間一直重試。Compose 預設只等 10 秒就改送 kill -9,所以結果和上一列相同

把這幾列對回安裝包,就是本篇開頭那段話的來源。主機 A 不通時,過了封頂、等著送往主機 A 的事件先在記憶體裡排隊;排隊的數量到了大約 5,000 筆(一個 500 筆的出口緩衝,加上一路上六個元件之間的通道),上游就會被擋住,而 ClickHouse 那一路和 od-bridge 那一路共用同一個上游,所以會一起停。注意這裡算的是「通過封頂的事件」,全域封頂一分鐘最多放 8 筆,5,000 筆至少要十個小時的持續攻擊才會累積到;一般的主機 A 重開機(幾分鐘)碰不到這條線。但主機 A 停一個週末、主機 B 又正好被掃,就會碰到,而且 ClickHouse 那一路停下來的時候,Suricata 與 WAF 的紀錄還在繼續寫檔,Vector 的 checkpoint 停在原地,等恢復後再從停住的地方追讀,所以檔案裡的不會漏,只是晚進去。

主機 A 恢復之後,排隊的事件會在幾秒內全部送出,這時主機 A 的受理端點會用它自己的限流(每把金鑰每分鐘 100 筆)把 Vector 擋回 429,Vector 再慢慢重送。所以恢復當下不會被打垮,但會看到案件在幾分鐘內一批一批冒出來,而且它們的「發生時間」是事件本來的時間,不是恢復的時間。

還有一個實測才看得到的細節:排在隊伍最前面的那一筆會被一直重試,後面的全部等它。在真實的節點上看到的樣子是 od-bridge 的 /forwards 頁同一個 correlation_id 每 20 到 30 秒出現一次、狀態 0,連續幾十個小時,而 Vector 的計數器顯示出口「收到」的數量停在隊伍前幾筆,不再增加。這些都是「主機 A 不通」的樣子,不是 Vector 壞了;主機 A 回來,它們自己會消失。

重啟 Vector 容器會丟掉排隊中的事件。 排隊的只存在記憶體,docker compose restart vector 與 install.sh --reconfigure 的重啟都一樣。這些事件在 ClickHouse 都有(它們是正規化之後才排隊的),丟掉的只是「它們本來會在主機 A 變成的案件」。要不要讓它們活過重啟,Vector 有磁碟緩衝可以開,安裝包沒有開:防禦節點的角色是「盡快告訴主機 A 現在有人在打」,隔了一個週末才送到的案件價值有限,而全量本來就在 ClickHouse。這是取捨,不是遺漏。

五、它不做的事

限制 說明
不偵測、不阻擋 它讀的是別人寫下的告警。WAF 擋不擋、Suricata 叫不叫,都在它之前就決定了
封頂是丟棄,不是聚合 第三節。超額的事件不會變成「這五分鐘共 N 筆」的一筆摘要,直接消失。真實次數查 ClickHouse
不存事件 第一節與第四節。它只記「讀到哪裡」,排隊中的事件在記憶體裡,重啟就沒了
WAF 事件只記第一條命中規則 一筆交易命中五條規則,rule_id 只有第一條,嚴重度取最嚴重的那條。其餘四條在 ClickHouse 的 raw 欄裡有,主機 A 看不到
後端的 4xx/5xx 只留在 ClickHouse 第三節。沒有命中規則的交易以嚴重度 1、rule_id 空白寫進 ClickHouse,不送主機 A。要看「有人在試登入」去 ClickHouse 查,要看後端活著沒去健康檢查
8686 埠沒有認證 能連的人可以即時看到流過的每一筆事件的全文。它是唯讀的,不能改設定、不能停元件,但看得到攻擊者 IP、網址、User-Agent。防火牆名單是唯一的門
到 od-bridge 之間沒有加密 Vector 在容器裡,od-bridge 在主機網路上,兩者之間是主機內部的 HTTP,Bearer token 明文。這段不出主機 B,所以沒有加
改設定要重啟 它有自動重載的選項,安裝包沒有開。改 .env 或 vector.yaml 之後要 docker compose restart vector,重啟會丟掉排隊中的事件與封頂的計數
輪替的那一秒會漏 第三節。主機上每小時清空一次紀錄檔,清空當下還沒讀走的幾行不見。安裝包的輪替腳本自己註明了這是已知限制

把它停掉會怎樣,不必實測也知道:主機 A 立刻收不到任何事件,ClickHouse 不再有新資料,Grafana 的圖停在停掉的時間;WAF 照常擋、Suricata 照常寫檔、EveBox 照常可以翻,因為那些都不經過它。重新啟動後,它從 checkpoint 接著讀,停機期間寫進檔案的告警會補進 ClickHouse 與主機 A,但 CrowdSec 在這段期間 POST 過來的通知沒有人收,那幾筆就沒了(CrowdSec 的通知外掛設定重試 3 次、每次等 10 秒,Vector 停超過半分鐘就追不回)。

六、怎麼確認它在做事

先看它活著、設定檔有沒有錯。第二個指令就是 install.sh --verify 裡「Vector 設定檔 OK」那一行在跑的東西:

# 主機 B
curl -s http://127.0.0.1:8686/health
{"ok":true}

cd /opt/integrated-waf
sudo docker compose exec -T vector vector validate /etc/vector/vector.yaml
√ Loaded ["/etc/vector/vector.yaml"]
√ Component configuration
~ Health check disabled for "bridge_intake"
~ Health check disabled for "ch_events"
                                  Validated

再看每個元件處理了幾筆。8686 埠的 API 是 GraphQL,瀏覽器開 http://192.168.0.112:8686/playground 可以互動查,命令列用 curl 問同一個問題。下面這段列出每個轉換元件「收到幾筆、送出幾筆」,兩個數字的差就是它丟掉的:

# 主機 B
python3 - <<'PY'
import json, urllib.request
q = ('{ transforms(first:100) { nodes { componentId metrics '
     '{ receivedEventsTotal { receivedEventsTotal } '
     '  sentEventsTotal { sentEventsTotal } } } } }')
req = urllib.request.Request('http://127.0.0.1:8686/graphql',
    data=json.dumps({'query': q}).encode(),
    headers={'Content-Type': 'application/json'})
for n in json.load(urllib.request.urlopen(req))['data']['transforms']['nodes']:
    m = n['metrics']
    r = (m['receivedEventsTotal'] or {}).get('receivedEventsTotal')
    s = (m['sentEventsTotal'] or {}).get('sentEventsTotal')
    print(f"{n['componentId']:28s} in={r} out={s}")
PY

看的重點是幾個轉折點。suricata_alerts_only 的 in 是 eve.json 的全部行數、out 只剩告警,差好幾個數量級是正常的;intake_filter 的 in 減 out 是被內網對內網與資訊級規則丟掉的;intake_throttle_suricata 的 in 減 out 是被封頂丟掉的。把 transforms 換成 sinks 看兩個出口:ch_events 的 in 應該等於 ch_events_from_ocsf 的 out;bridge_intake 的 in 應該等於 intake_throttled_clean 的 out,如果 in 明顯比它少、而且不再增加,就是第四節的情況,去看主機 A。這些數字從容器啟動時開始累計,重啟歸零。

要看事件本身,用它自帶的 tap,指定看某個元件的輸出。這就是 8686 埠「看得到每一筆」的意思:

# 主機 B:看 6 秒內離開 intake_filter 的事件(也就是將要送往主機 A 的)
sudo docker compose exec -T vector vector tap --url http://127.0.0.1:8686/graphql \
  --duration-ms 6000 --outputs-of intake_filter --format json

要驗「過濾」這一道有在做事,可以從測試注入口丟一筆內網對內網的事件。它會進 ClickHouse,但不會出現在 od-bridge 的轉送紀錄裡,主機 A 也不會多一張單。這是實測過的:

# 主機 B
curl -s -X POST -H 'Content-Type: application/json' http://127.0.0.1:8688/ --data '{
  "correlation_id":"11111111-2222-4333-8444-555555555555","source_system":"vector",
  "event_class":"web_activity","occurred_at":"2026-10-11T00:00:00Z","severity_id":2,
  "finding":{"title":"filter test","rule_id":"test-internal","rule_set":"test"},
  "actor":{"ip":"192.168.0.5"},"target":{"host":"192.168.0.112","url":"/","service":"test"},
  "evidence":{"raw_log_ref":{"type":"test","locator":"x"},"snippet":""}}'

# 幾秒後:ClickHouse 有,od-bridge 沒有
sudo docker compose exec -T clickhouse clickhouse-client -q \
  "SELECT source_system, actor_ip, target_host FROM secstack.events WHERE correlation_id='11111111-2222-4333-8444-555555555555'"
vector	::ffff:192.168.0.5	192.168.0.112
curl -s http://127.0.0.1:8500/forwards | grep -c 11111111-2222
0

把攻擊者 IP 換成一個公網位址再送一次,它就會出現在 /forwards,主機 A 多一張單。install.sh --test-event 做的就是這件事,攻擊者固定是 203.0.113.42。

最後是它的紀錄。正常運作時 Vector 幾乎不寫紀錄,有東西出現就值得看:

紀錄裡出現 意思
Retrying after response. reason=Http Status: 502 Bad Gateway,每 30 秒一行 od-bridge 連不上主機 A。看 od-bridge 的紀錄會有 Cannot connect to host。第四節
Retrying after response. reason=Http Status: 401 od-bridge 不認 Vector 的 token,.env 的 BRIDGE_INGEST_TOKEN 改過但只重啟了一邊。兩個容器都重啟
Internal log [...] is being suppressed to avoid flooding 同一種警告太多,它自己壓掉了。不是新問題,是上面那個還在
function call error for "parse_json" 讀到一行不是 JSON 的紀錄,那一筆丟掉,其餘照常。Suricata 或 WAF 寫檔被截斷時偶爾會有一兩行
什麼都沒有,但 ClickHouse 沒有新資料 先看 suricata_eve 與 modsec_audit 兩個來源的計數器有沒有在動。沒動,問題在上游(Suricata 沒在收封包、WAF 沒有流量),不在 Vector

Vector 在整體裡的位置到此說完。它是這台主機上唯一一個「所有告警都要經過」的元件,但它自己不留任何東西:進來的是三種格式的檔案與通知,出去的是一種格式的兩份副本,一份全量、一份篩過。平台上案件的多寡、攻擊者 IP 認的對不對、主機 A 停機期間會不會漏,答案都在那 461 行設定檔裡,而不在任何一個偵測引擎裡。搭配元件篇到這裡是八篇;安裝包裡唯一自寫的程式 od-bridge,另有專篇。


上一篇
Portainer:容器的管理畫面
下一篇
od-bridge:事件從這裡出去,決策從這裡回來
系列文
一鍵完成六套開源防禦系統整合 共 28 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言